package edu.gatech.cc.liam.geometry.linear;

import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TreeMap;

public class SparsePoint extends Vector {
	int size;
	Integer hashCache = null; 
	private TreeMap<Integer, Double> nonZeros;
	
	public SparsePoint(int size) {
		this.size = size;
		nonZeros = new TreeMap<Integer, Double>();
	}
	public SparsePoint(double[] values) {
		this.size = values.length;
		nonZeros = new TreeMap<Integer, Double>();
		for(int i=0; i<values.length; i++) {
			if(values[i] != 0.0)
				nonZeros.put(i, values[i]);
		}
	}
	private SparsePoint(SparsePoint toClone) {
		this.size = toClone.size;
		this.nonZeros = new TreeMap<Integer, Double>(toClone.nonZeros);
	}
	
	@Override
	public Iterator<Entry<Integer, Double>> iterator() {
		return nonZeros.entrySet().iterator();
	}

	@Override
	public double get(int i) {
		Double value = nonZeros.get(i);
		return value==null ? 0.0 : value;
	}

	@Override
	public void set(int i, double value) {
		if(value != 0.0) {
			nonZeros.put(i, value);
		} else {
			nonZeros.remove(i);
		}
		hashCache = null;
	}

	@Override
	public int dimensions() {
		return size;
	}
	@Override
	public int getNumEntries() {
		return this.nonZeros.size();
	}
	@Override
	public void scale(Double scaleFactor) {
		for(Entry<Integer, Double> e : nonZeros.entrySet()) {
			e.setValue(e.getValue() * scaleFactor);
		}
		hashCache = null;
	}
	@Override
	public SparsePoint clone() {
		return new SparsePoint(this);
	}
	
	@Override
	public String toString() {
		return nonZeros.toString();
	}
	@Override
	public boolean softEquals(Vector anotherPoint, double tolerance) {
	    Iterator<Entry<Integer, Double>> myIter = this.iterator();
	    Iterator<Entry<Integer, Double>> OtherIter = anotherPoint.iterator();
	    while ( myIter.hasNext() && OtherIter.hasNext() ){
	    	Entry<Integer, Double> e1 = myIter.next();
	    	Entry<Integer, Double> e2 = OtherIter.next();
	    	if(e1.getKey() != e2.getKey())
	    		return false;
	    	if(Math.abs(e1.getValue() - e2.getValue()) > tolerance)
	    		return false;
	    }
	    return !myIter.hasNext() && !OtherIter.hasNext();
	}
	@Override
	public int hashCode() {
		if(hashCache != null)
			return hashCache;
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((nonZeros == null) ? 0 : nonZeros.hashCode());
		result = prime * result + size;
		hashCache = result;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SparsePoint other = (SparsePoint) obj;
		if (nonZeros == null) {
			if (other.nonZeros != null)
				return false;
		} else if (!nonZeros.equals(other.nonZeros))
			return false;
		if (size != other.size)
			return false;
		return true;
	}

	public double dotProduct(Vector v) {
		if(this.getNumEntries() <= v.getNumEntries()) {
			return this.dotProductTraverse(v);
		} else {
			return v.dotProduct(this);
		}	
	}
	private double dotProductTraverse(Vector v) {
		assert v.dimensions() == this.dimensions();
		double sum = 0;
		for(Entry<Integer, Double> e : this) {
			int i = e.getKey();
			double value = e.getValue();
			sum += v.get(i) * value;
		}
		return sum;
	}
//	public double dotProductCloseSizes(Vector v) {
//		assert v.dimensions() == this.dimensions();
//	    Iterator<Entry<Integer, Double>> myIter = this.iterator();
//	    Iterator<Entry<Integer, Double>> OtherIter = v.iterator();
//	    double sum = 0;
//	    if (myIter.hasNext() && OtherIter.hasNext()) {
//	    	Entry<Integer, Double> e1,e2;
//	    	e1 = myIter.next();
//	    	e2 = OtherIter.next();	    	
//		    while ( myIter.hasNext() && OtherIter.hasNext() ){
//		    	if(e1.getKey() == e2.getKey()) {
//		    		sum += e1.getValue() * e2.getValue();
//			    	e1 = myIter.next();
//			    	e2 = OtherIter.next();
//		    	} else if(e1.getKey() > e2.getKey()) {
//		    		e2 = OtherIter.next();
//		    	} else { // e1.getKey() < e2.getKey()
//		    		e1 = myIter.next();
//		    	}
//		    }
//	    }
//		return sum;
//	}
}
